#include <codex/os/system/CDX_EventManager.h>
#include <codex/os/CDX_Timer.h>
#include <memory>

namespace codex
{
    //const tString HashedString::kpWildcardEventType = "kpWildcardEventType";
	// one global instance 
	typedef std::set< EventType > EventTypeSet;
	// insert result into event type set 
	typedef std::pair< EventTypeSet::iterator, tBOOL > EventTypeSetIRes;
	// one list per event type ( stored in the map ) 
	typedef std::list< EventListenerPtr > EventListenerTable;
	// mapping of event ident to listener list 
	typedef std::map< tU32, EventListenerTable > EventListenerMap;
	// entry in the event listener map 
	typedef std::pair< tU32, EventListenerTable > EventListenerMapEnt;
	// insert result into listener map 
	typedef std::pair< EventListenerMap::iterator, tBOOL > EventListenerMapIRes;
	// queue of pending- or processing-events 
	typedef std::list< IEventDataPtr > EventQueue;

	enum eConstants { kNumQueues = 2, kINFINITE = 0xffffffff };

	// list of registered event types 
	static EventTypeSet m_typeList;
	// mapping of event types to listeners 
	static EventListenerMap m_registry;
	// double buffered event processing queue (prevents infinite cycles) 
	static EventQueue m_queues[kNumQueues];
	// which queue is actively processing, en-queing events 
	// goes to the opposing queue 
	static int m_activeQueue = 0;

	tBOOL eventManager::registerEventType(EventType const & inType)
	{

		if (0 == inType.getStr().length())
			return FALSE;

		if ((inType.getHashValue() == 0) && (cdxStrcmp(inType.getStr().c_str(), kpWildcardEventType.c_str()) != 0))
			return FALSE;

		EventTypeSet::const_iterator evIt = m_typeList.find(inType);
		// no lo encontro
		if (evIt == m_typeList.end())
		{
			m_typeList.insert(inType);
			return TRUE;
		}

		return FALSE;
	}

	tBOOL eventManager::ValidateType(EventType const &inType) 
	{ 
		if (0 == inType.getStr().length() ) 
			return FALSE; 

		if ((inType.getHashValue() == 0) && (cdxStrcmp(inType.getStr().c_str(), kpWildcardEventType.c_str()) != 0))
			return FALSE; 
		
		EventTypeSet::const_iterator evIt = m_typeList.find(inType); 
		if (evIt == m_typeList.end()) 
		{ 
			cdxWarning(TRUE, "Failed validation of an event type; it was probably not registered with the EventManager!"); 
			return FALSE; 
		} 
		return TRUE; 
	}

	tBOOL eventManager::addListener(EventListenerPtr const& inListener, const char* eventName ) {

		EventType inType = EventType(eventName);

		if (!ValidateType(inType)) 
			return FALSE; 
		// check / update type list 
		EventTypeSet::iterator evIt = m_typeList.find( inType );
		// find listener map entry, create one if no table already 
		// exists for this entry ... 
		EventListenerMap::iterator elmIt = m_registry.find(inType.getHashValue());
		if ( elmIt == m_registry.end() ) { 
			EventListenerMapIRes elmIRes = m_registry.insert( EventListenerMapEnt( inType.getHashValue(), EventListenerTable() ) ); 
			// whoops, could not insert into map!?!? 
			if ( elmIRes.second == false ) 
				return FALSE; 
			// should not be possible, how did we insert and create 
			// an empty table!?!?!
			if (elmIRes.first == m_registry.end()) 
				return FALSE; 
			// store it so we can update the mapped list next ... 
			elmIt = elmIRes.first; 
		} 
		// update the mapped list of listeners, walk the existing 
		// list (if any entries) to prevent duplicate addition of 
		// listeners. This is a bit more costly at registration time 
		// but will prevent the hard-to-notice duplicate event 
		// propogation sequences that would happen if double-entries 
		// were allowed. 
		// note: use reference to make following code more simple 
		EventListenerTable & evlTable = (*elmIt).second; 

		for ( EventListenerTable::iterator it = evlTable.begin(), itEnd = evlTable.end(); it != itEnd ; it++ ) 
		{ 
			tBOOL bListenerMatch = ( *it == inListener ); 
			if ( bListenerMatch ) 
				return FALSE; 
		} 
		// okay, event type validated, event listener validated, 
		// event listener not already in map, add it 
		evlTable.push_back( inListener ); 
		return TRUE; 
	}

	tBOOL eventManager::removeListener(EventListenerPtr const & inListener, const char* eventName) {

		EventType inType = EventType(eventName);

		if (!ValidateType(inType))
			return FALSE;

		tBOOL rc = FALSE; 
		// brute force method, iterate through all existing mapping 
		// entries looking for the matching listener and remove it. 
		for ( EventListenerMap::iterator it = m_registry.begin(), itEnd = m_registry.end(); it != itEnd; it++ ) 
		{ 
			//tU32 const kEventId = it->first;
			EventListenerTable & table = it->second; 
			for ( EventListenerTable::iterator it2 = table.begin(), it2End = table.end(); it2 != it2End; it2++ ) 
			{ 
				if (*it2 == inListener) { 
					// found match, remove from table, 
					table.erase( it2 ); 
					// update return code 
					rc = TRUE; 
					// and early-quit the inner loop as addListener() 
					// code ensures that each listener can only 
					// appear in one event's processing list once. 
					break; 
				} 
			} 
		} 
		return rc; 
	}

	tBOOL eventManager::trigger(IEventData const & inEvent){
		if (!ValidateType(inEvent.GetEventType())) 
			return false; 
		
		EventListenerMap::const_iterator itWC = m_registry.find(0); 

		if (itWC != m_registry.end()) { 
			EventListenerTable const & table = itWC->second; 
			//tBOOL processed = FALSE;
			for (EventListenerTable::const_iterator it2 = table.begin(), it2End = table.end(); it2 != it2End; it2++) {
				(*it2)->HandleEvent(inEvent);
				//(*it2)(inEvent);
			} 
		} 

		EventListenerMap::const_iterator it = m_registry.find(inEvent.GetEventType().getHashValue()); 
		if (it == m_registry.end()) 
			return FALSE; 
		
		EventListenerTable const & table = it->second; 
		tBOOL processed = FALSE; 
		//EventListenerPtr listener;
		for (EventListenerTable::const_iterator it2 = table.begin(), it2End = table.end(); it2 != it2End; it2++) {
			//EventListenerPtr listener = *it2;
			if ((*it2)->HandleEvent(inEvent)) {
			//if ((*it2)(inEvent)) {
				// only set to true, if processing eats the messages 
				processed = TRUE; 
			} 
		} 
		return processed; 
	}
	
	tBOOL eventManager::queueEvent(IEventDataPtr const & inEvent) {
		cdxAssert(m_activeQueue >= 0); 
		cdxAssert(m_activeQueue < kNumQueues); 

		if (!ValidateType(inEvent->GetEventType()))
			return FALSE; 
		
		EventListenerMap::const_iterator it = m_registry.find(inEvent->GetEventType().getHashValue()); 
		
		if (it == m_registry.end()) { 
			// if global listener is not active, then abort queue add 
			EventListenerMap::const_iterator itWC = m_registry.find( 0 ); 
			if ( itWC == m_registry.end() ) 
			{ 
				// no listeners for this event, skipit 
				return FALSE; 
			} 
		} 
		m_queues[m_activeQueue].push_back( inEvent ); 
		return TRUE; 
	}
		

	tBOOL eventManager::abortEvent(EventType const & inType, tBOOL allOfType) {
		cdxAssert(m_activeQueue >= 0);
		cdxAssert(m_activeQueue < kNumQueues); 

		if (!ValidateType(inType)) 
			return false; 
		
		EventListenerMap::iterator it = m_registry.find(inType.getHashValue());

		if (it == m_registry.end()) 
			return false; 
		
		// no listeners for this event, skipit 
		tBOOL rc = FALSE; 
		EventQueue &evtQueue = m_queues[m_activeQueue]; 

		for ( EventQueue::iterator it = evtQueue.begin(), itEnd = evtQueue.end(); it != itEnd; it++ ) 
		{ 
			if ( (*it)->GetEventType() == inType ) 
			{ 
				it = evtQueue.erase(it); 
				rc = TRUE; 

				if ( !allOfType ) 
					break; 
			} else { 
				++it; 
			} 
		} 
		return rc; 
	}

	//=======================================
	tBOOL eventManager::Tick(tU32 maxMillis) {
		// TODO: sustituir por codex::timer::getCurrentTime() o algo por el estilo
		tU64 curMs = codex::system::timer::getCurrentTime(); //GetTickCount(); 

		tU64 maxMs = (maxMillis == eConstants::kINFINITE) ? eConstants::kINFINITE : (curMs + maxMillis);
		
		EventListenerMap::const_iterator itWC = m_registry.find(0); 
		// swap active queues, make sure new queue is empty after the swap ... 
		int queueToProcess = m_activeQueue; 
		m_activeQueue = ( m_activeQueue + 1 ) % kNumQueues;
		m_queues[m_activeQueue].clear(); 

		// now process as many events as we can ( possibly time limited ) ... 
		// always do AT LEAST one event, if ANY are available ... 
		while ( m_queues[queueToProcess].size() > 0 ) { 
			IEventDataPtr event = m_queues[queueToProcess].front(); 
			m_queues[queueToProcess].pop_front(); 
			EventType const & eventType = event->GetEventType(); 
			EventListenerMap::const_iterator itListeners = m_registry.find( eventType.getHashValue() ); 
			
			if ( itWC != m_registry.end() ) { 
				EventListenerTable const & table = itWC->second; 
				//tBOOL processed = FALSE;
				
				for ( EventListenerTable::const_iterator it2 = table.begin(), it2End = table.end(); 
					it2 != it2End; it2++ ) { 
					(*it2)->HandleEvent(*event);
					//(*it2)(*event);
				} 
			} 
			// no listerners currently for this event type, skip it 
			if ( itListeners == m_registry.end() ) 
				continue; 
			
			//tU32 const kEventId = itListeners->first;
			EventListenerTable const & table = itListeners->second; 
			for ( EventListenerTable::const_iterator it = table.begin(), end = table.end(); it != end ; it++ ) { 
				if ((*it)->HandleEvent(*event)) {
				//if ( (*it)(*event) ){
					break; 
				}
			} 
		
			// TODO: sustituir por metodo codex::timer::getTickCount();
			curMs = codex::system::timer::getCurrentTime(); //GetTickCount();
			if (maxMillis != eConstants::kINFINITE) {
				if (curMs >= maxMs) { 
					// time ran about, abort processing loop 
					break; 
				} 
			} 
		} 
		// if any events left to process, push them onto the active 
		// queue. 
		// 
		// Note: to preserve sequencing, go bottom-up on the 
		// remainder, inserting them at the head of the active 
		// queue... 
		bool queueFlushed = ( m_queues[queueToProcess].size() == 0 ); 
		if ( !queueFlushed ) { 
			while ( m_queues[queueToProcess].size() > 0 ) { 
				IEventDataPtr event = m_queues[queueToProcess].back(); 
				m_queues[queueToProcess].pop_back();
				m_queues[m_activeQueue].push_front( event ); 
			} 
		} 
		// all done, this pass
		return queueFlushed; 
	}

	EventListenerList eventManager::getListenerList(EventType const & eventType){ 
		// invalid event type, so sad 
		if ( ! ValidateType( eventType ) ) 
			return EventListenerList();

		EventListenerMap::const_iterator itListeners = m_registry.find(eventType.getHashValue()); 
		
		// no listerners currently for this event type, so sad 
		if ( itListeners == m_registry.end() ) 
			return EventListenerList(); 
		
		EventListenerTable const & table = itListeners->second; 
		// there was, but is not now, any listerners currently for 
		// this event type, so sad 
		if ( table.size() == 0 ) 
			return EventListenerList(); 
		
		EventListenerList result; 
		result.reserve( table.size() ); 
		for ( EventListenerTable::const_iterator it = table.begin(), end = table.end(); it != end ; it++ ) { 
			result.push_back( (*it)); 
		} 
		
		return result; 
	} 
	
	EventTypeList eventManager::getTypeList ( void ) { 
		// no entries, so sad 
		if ( m_typeList.size() == 0 ) 
			return EventTypeList(); 
		
		EventTypeList result; 
		result.reserve( m_typeList.size() ); 
		
		for ( EventTypeSet::const_iterator it = m_typeList.begin(), itEnd = m_typeList.end(); it != itEnd; it++ ) { 
			result.push_back( *it ); 
		} 

		return result; 
	}

}